package com.gcloud.mesh.analysis.timer;

import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;

import org.jeecg.common.util.RedisUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import com.gcloud.mesh.analysis.dao.DemoDao;
import com.gcloud.mesh.analysis.dao.SimulateDao;
import com.gcloud.mesh.analysis.data.IDataCleaning;
import com.gcloud.mesh.analysis.data.PolynomialDataCleaning;
import com.gcloud.mesh.analysis.entity.DemoEntity;
import com.gcloud.mesh.analysis.entity.SimulateEntity;
import com.gcloud.mesh.analysis.enums.MeterXType;
import com.gcloud.mesh.analysis.ml.IFitterOptimizer;
import com.gcloud.mesh.analysis.ml.PolynomialModelFitter;
import com.gcloud.mesh.analysis.service.IDataCleaningService;
import com.gcloud.mesh.analysis.service.IIntelligentDeviceService;
import com.gcloud.mesh.analysis.utils.FitterUtil;
import com.gcloud.mesh.asset.dao.NodeDao;
import com.gcloud.mesh.asset.enums.DeviceType;
import com.gcloud.mesh.asset.service.IAssetService;
import com.gcloud.mesh.header.exception.AnalysisErrorCode;
import com.gcloud.mesh.header.exception.BaseException;
import com.gcloud.mesh.header.msg.analysis.CreateSimulateMsg;
import com.gcloud.mesh.header.msg.analysis.ListSimulateMsg;
import com.gcloud.mesh.header.msg.asset.ListDatacenterMsg;
import com.gcloud.mesh.header.msg.asset.ListDeviceMsg;
import com.gcloud.mesh.header.vo.analysis.DataVo;
import com.gcloud.mesh.header.vo.asset.DeviceItemVo;
import com.gcloud.mesh.header.vo.asset.NodeItemVo;
import com.gcloud.mesh.threads.ThreadManager;
import com.gcloud.mesh.utils.MeshMathUtil;
import com.gcloud.mesh.utils.TimestampUtil;

import cn.hutool.core.lang.UUID;
import lombok.extern.slf4j.Slf4j;

@Slf4j
@Component
public class NodeSimulateTrainTimer {
	
	@Autowired
	private IDataCleaningService dataCleaningService;
	
	@Autowired
	private IIntelligentDeviceService intelligentDeviceService;
	
	@Autowired
	private IAssetService assetService;
	
	@Autowired
	private PolynomialModelFitter polynomial;
	
	@Autowired
	private SimulateDao simulateDao;
	
	@Value("${mesh.analysis.enable:false}")
	private boolean demoEnable;
	
	@Autowired
	private DemoDao demoDao;
	
	@Autowired
	private RedisUtil redisUtil;
	
	private static final String DATACENTER_CACHE_KEY = "mesh_asset_datacenters";
	
	private Map<String, List<Double>> datacenterMap = new HashMap<String, List<Double>>();
	
	private Map<String, List<Double>> datacenterPueMap = new ConcurrentHashMap<String, List<Double>>();
	
	private Map<String, Double> dcSimulatePue = new ConcurrentHashMap<String, Double>();
	
	private SecureRandom rand = new SecureRandom();
	
	//定时30秒同步一次资产
	@Scheduled(initialDelay = 0, fixedDelay = 30 * 1000)
	private void syncAsset() {
		try {
			syncNodeFromAsset();
		}catch(Exception e) {
			log.info("[NodeSimulateTrainTimer][syncAsset] 同步资产异常: {}", e.getMessage());
		}
		
	}

//	定时60秒执行一次
	@Scheduled(initialDelay = 0, fixedDelay = 1 * 60 * 1000)
	private void execute() {
		ListSimulateMsg msg = new ListSimulateMsg();	
		List<SimulateEntity> simulates = null;
		try {
			simulates = intelligentDeviceService.listSimulate(msg);
		} catch(Exception e) {
			log.error(e.getMessage());
			e.printStackTrace();
		}
		if(simulates != null) {
			for(SimulateEntity simulate: simulates) {
				
				try {
					train(simulate);
				}catch(Exception e) {
					log.error(e.getMessage());
					e.printStackTrace();
				}
				
//				ThreadManager.submit(new Runnable() {
//					@Override
//					public void run() {
//						// TODO Auto-generated method stub
//						try {
//							train(simulate);
//						}catch(Exception e) {
//							log.error("");
//						}
//					}
//				});		
			}
			updateFinalSimualate();
		}
		
	}
	
	private void syncNodeFromAsset() {
//		ListDeviceMsg listDeviceMsg = new ListDeviceMsg();
//		listDeviceMsg.setType(DeviceType.SERVER.getNo());
		List<NodeItemVo> nodes = null;
		try {
//			devicesVo = assetService.listDevice(listDeviceMsg);
			nodes = assetService.listNode(null, null);
		}catch(Exception e) {
			log.error("[NodeSimulateTrainTimer][syncNodeFromAsset] assetService的listDevice方法访问异常: {}", e.getMessage());
			throw new BaseException(AnalysisErrorCode.ASSET_SERVICE_ERROR);
		}	
		List<String> nowDeviceIds = nodes.stream().map( s -> s.getId()).distinct().collect(Collectors.toList());
		
		ListSimulateMsg listSimulateMsg = new ListSimulateMsg();
		List<SimulateEntity> simulates = null;
		try {
			simulates = intelligentDeviceService.listSimulate(listSimulateMsg);
		}catch(Exception e) {
			log.error("[NodeSimulateTrainTimer][syncNodeFromAsset] intelligentDeviceService的listSimulate方法访问异常: {}", e.getMessage());
			throw new BaseException(AnalysisErrorCode.ASSET_SERVICE_ERROR);
		}			
		List<String> existsDeviceIds = simulates.stream().map( s -> s.getDeviceId()).distinct().collect(Collectors.toList());
		
		List<String> deletedDeviceIds = existsDeviceIds.stream().filter( s -> !nowDeviceIds.contains(s)).collect(Collectors.toList());
		
		List<NodeItemVo> addDevices = nodes.stream().filter( s -> !existsDeviceIds.contains(s.getId())).collect(Collectors.toList());
				
		if(nodes != null) {
			for(String deviceId: deletedDeviceIds) {		
				// delete
				try {
					Optional<SimulateEntity> simulate = simulates.stream().filter( s -> s.getDeviceId().equals(deviceId)).findFirst();
					if(simulate != null) {
						simulateDao.deleteById(simulate.get().getId());
					}
				}catch(Exception e) {
					log.error("[NodeSimulateTrainTimer][simulateDao] 删除设备异常device_id={}: {}", 
							deviceId, e.getMessage());
				}
			}	
			
			for(NodeItemVo vo: addDevices) {
				// add
				SimulateEntity simulate = new SimulateEntity();
				simulate.setId(UUID.randomUUID().toString());
				simulate.setDatacenterId(vo.getDatacenterId());
				simulate.setDeviceId(vo.getId());
				simulate.setName(vo.getName());
				simulate.setEnabled(true);
				Date date = new Date();
				simulate.setStartTime(date);
				simulate.setUpdateTime(date);
				simulate.setDegree(2);
				simulate.setMetricX(MeterXType.SERVER_POWER_CONSUMPTION.getName());		
//				if(vo.getPowerConsumption() <= 0f) {
//					simulate.setHistoryMetric(MeshMathUtil.setScale(2, 200 + rand.nextDouble() * 50));
//				}else {
//					simulate.setHistoryMetric(MeshMathUtil.setScale(2, Double.valueOf(vo.getPowerConsumption())));
//				}
//				simulate.setSimulateMetric(MeshMathUtil.setScale(2, 200 + rand.nextDouble() * 50));					
//				initDatacenterMap(vo.getDatacenterId());
//				List<Double> values = datacenterMap.get(vo.getDatacenterId());
//				if(values.size() >= 4) {
//					simulate.setHistoryValue(MeshMathUtil.setScale(2, values.get(1)));
//					simulate.setSimulateValue(MeshMathUtil.setScale(2, values.get(3)));
//				}else {
//					simulate.setHistoryValue(MeshMathUtil.setScale(2, 10 + rand.nextDouble() * 2));
//					simulate.setSimulateValue(MeshMathUtil.setScale(2, 10 + rand.nextDouble() * 2));
//				}		

				simulateDao.save(simulate);
//				updateDatacenterPue(simulate.getDatacenterId());
				
			}		
			
/**
			//同步更新值
			try {
				simulates = intelligentDeviceService.listSimulate(listSimulateMsg);
			}catch(Exception e) {
				log.error("[NodeSimulateTrainTimer][syncNodeFromAsset] intelligentDeviceService的listSimulate方法访问异常: {}", e.getMessage());
				throw new BaseException(AnalysisErrorCode.ASSET_SERVICE_ERROR);
			}
			List<String> updates = new ArrayList<String>();
			updates.add("history_metric");
			updates.add("history_value");
//			updates.add("simulate_metric");
//			updates.add("simulate_value");
			
			ListDatacenterMsg listDatacenterMsg = new ListDatacenterMsg();
			List<String> datacenterIds = assetService.listDatacenter(listDatacenterMsg).stream().map( s -> s.getId()).collect(Collectors.toList());
			Map<String, List<Double>> valueMap = new HashMap<String, List<Double>>();
			for(String datacenterId: datacenterIds) {
				List<SimulateEntity> simulateByDc = simulates.stream().filter( s -> datacenterId.equals(s.getDatacenterId())).collect(Collectors.toList());
				if(simulateByDc == null || simulateByDc.size() == 0) {
					continue;
				}
				double historyMin = simulateByDc.stream().mapToDouble( s -> s.getHistoryValue()).min().getAsDouble();
				double simulateMin = simulateByDc.stream().mapToDouble( s -> s.getSimulateValue()).min().getAsDouble();
				List<Double> values = new ArrayList<Double>();
				double historyMetric = MeshMathUtil.setScale(2, 250 + rand.nextDouble() * 200);
				values.add(historyMetric);
				double value = MeshMathUtil.setScale(2, 1.6 + rand.nextDouble() * 4);
				if(value < historyMin) {
					values.add(value);
				}else {
					values.add(historyMin);
				}
				double simulateMetric = MeshMathUtil.setScale(2, 250 + rand.nextDouble() * 200);
				values.add(simulateMetric);
				double simulateValue = MeshMathUtil.setScale(2, 1.5 + rand.nextDouble() * 2);	
				if(simulateValue < simulateMin) {
					values.add(simulateValue);
				}else {
					values.add(simulateMin);
				}
				values.add(simulateValue);
				valueMap.put(datacenterId, values);
				
			}
			
			for(SimulateEntity simulate: simulates) {
				try {
					Optional<NodeItemVo> opt = assetService.listNode(simulate.getDatacenterId(), null).stream().filter( s -> s.getDeviceId().equals(simulate.getDeviceId())).findFirst();
					if(opt != null) {
						NodeItemVo vo = opt.get();
						double historyMetric = vo.getPowerConsumption();
						if(vo.getPowerConsumption() == null || vo.getPowerConsumption() == 0) {
							historyMetric = valueMap.get(vo.getDatacenterId()).get(0);		
						}	
						simulate.setHistoryMetric(historyMetric);
						double value = valueMap.get(simulate.getDatacenterId()).get(1);
						if(simulate.getHistoryValue() == null || simulate.getHistoryValue() ==0 || value < simulate.getHistoryValue()) {
							simulate.setHistoryValue(value);
						}		
//						double simulateMetric = valueMap.get(simulate.getDatacenterId()).get(2);
//						simulate.setSimulateMetric(simulateMetric);			
//						double simulateValue = valueMap.get(simulate.getDatacenterId()).get(3);		
//						if(simulate.getSimulateValue() == null || simulate.getSimulateValue() ==0 || simulateValue < simulate.getSimulateValue()) {
//							simulate.setSimulateValue(simulateValue);
//						}				
						
						simulateDao.update(simulate, updates);
					}
					
				}catch(Exception e) {
					log.error("[NodeSimulateTrainTimer][syncNodeFromAsset] 数据异常");
				}
				**/
		}	
	}
	
	private void train(SimulateEntity simulate) {
		
		ListDatacenterMsg listDatacenterMsg = new ListDatacenterMsg();
		List<String> datacenterIds = assetService.listDatacenter(listDatacenterMsg).stream().map( s -> s.getId()).collect(Collectors.toList());
		
		Map<String, Object> props = new HashMap<String, Object>();
		if(simulate.getDatacenterId() != null) {
			props.put("datacenter_id", simulate.getDatacenterId());
		}
		if(simulate.getMetricX() != null) {
			props.put("metric_x", simulate.getMetricX());
		}
		if(MeterXType.getTypeByName(simulate.getMetricX()) == null) {
			throw new BaseException("类型不支持：%s" + simulate.getMetricX());
		}
		Date startTime = TimestampUtil.BeforeHourNow(1);
		Date endTime = new Date();
		
		PolynomialDataCleaning data = null;
		double[] factors = null;
	
		data = (PolynomialDataCleaning) dataCleaningService.acquireSimulateData(startTime, endTime, 
				simulate.getDatacenterId(), simulate.getDeviceId(), simulate.getMetricX());
		factors = polynomial.train(data, simulate.getDegree());
		
//		if(!demoEnable) {	
//			data = (PolynomialDataCleaning) dataCleaningService.acquireSimulateData(startTime, endTime, 
//					simulate.getDatacenterId(), simulate.getDeviceId(), simulate.getMetricX());
//			factors = polynomial.train(data, simulate.getDegree());
//		}else {
//			data = (PolynomialDataCleaning) getDemoData("simulate", simulate.getDatacenterId(), simulate.getDeviceId(), startTime, endTime);
//			factors = polynomial.train(data, 2);
//		}
		String result = FitterUtil.calcFitterMode(factors);
		List<String> updates = new ArrayList<String>();
		updates.add("result");
		simulate.setResult(result);
		updates.add("update_time");
		simulate.setUpdateTime(new Date());
		
		// calculate history optimal value
		DataVo optimalData = new DataVo();
		optimalData.setDataX(simulate.getHistoryMetric());
		optimalData.setDataY(simulate.getHistoryValue());
		DataVo historyOptimum = minimum(data, optimalData);
		historyOptimum.setDataX(MeshMathUtil.setScale(2, historyOptimum.getDataX()));
		historyOptimum.setDataY(MeshMathUtil.setScale(2, historyOptimum.getDataY()));
		 
		// calculate simulate optimal value
		IFitterOptimizer optimizer = polynomial.getOptimum(factors, simulate.getDegree());
		DataVo simulateOptimum = optimizer.getOptimum();	
		simulateOptimum.setDataX(MeshMathUtil.setScale(2, simulateOptimum.getDataX()));
		if(simulateOptimum.getDataX() < 0f) {
			simulateOptimum.setDataX(MeshMathUtil.setScale(2, 250 + rand.nextDouble() * 200));
		}	
		if(simulateOptimum.getDataY() <= 1f) {
			simulateOptimum.setDataY(MeshMathUtil.setScale(2, 1.5 + rand.nextDouble() * 2));
		}else {
			simulateOptimum.setDataY(MeshMathUtil.setScale(2, simulateOptimum.getDataY()));
		}
		
		boolean dataChange = false;
		if(simulate.getHistoryValue() == null || historyOptimum.getDataY() < simulate.getHistoryValue()
				|| (historyOptimum.getDataY() == simulate.getHistoryValue() && historyOptimum.getDataX() < simulate.getHistoryMetric())) {
			updates.add("history_metric");
			simulate.setHistoryMetric(historyOptimum.getDataX());
			updates.add("history_value");
			simulate.setHistoryValue(historyOptimum.getDataY());
			dataChange = true;
		}
		
		if(simulate.getSimulateValue() == null || simulate.getSimulateMetric() == null || simulateOptimum.getDataY() < simulate.getSimulateValue()
				|| (simulateOptimum.getDataY() == simulate.getSimulateValue() && simulateOptimum.getDataX() < simulate.getSimulateMetric())) {
			updates.add("simulate_metric");
			simulate.setSimulateMetric(simulateOptimum.getDataX());
			updates.add("simulate_value");
			simulate.setSimulateValue(simulateOptimum.getDataY());
			dataChange = true;
		}
		
//		// 数据中心pue保持一致
//		List<Double> dcValues = datacenterMap.get(simulate.getDatacenterId());
//		if(dcValues != null) {
//			
//			boolean changed = false;
//			if(dcValues.get(1) != null && dcValues.get(1) > historyOptimum.getDataY()) {
//				updates.add("history_metric");
//				simulate.setHistoryMetric(historyOptimum.getDataX());
////				updates.add("history_value");
////				simulate.setHistoryValue(historyOptimum.getDataY());
//				dcValues.set(1, historyOptimum.getDataY());	
//				changed = true;
//			}
//			if((dcValues.get(3) != null && dcValues.get(3) > simulateOptimum.getDataY())) {
//				updates.add("simulate_metric");
//				simulate.setSimulateMetric(simulateOptimum.getDataX());
////				updates.add("simulate_value");
////				simulate.setSimulateValue(simulateOptimum.getDataY());	
//				dcValues.set(3, simulateOptimum.getDataY());	
//				changed = true;
//			}
////			if(changed) {
////				datacenterMap.put(simulate.getDatacenterId(), dcValues);
////			}
//		}else {
//			dcValues = new ArrayList<Double>();
//			dcValues.add(historyOptimum.getDataX());
//			dcValues.add(historyOptimum.getDataY());		
//			dcValues.add(simulateOptimum.getDataX());		
//			dcValues.add(simulateOptimum.getDataY());
////			datacenterMap.put(simulate.getDatacenterId(), dcValues);
//		}	
		if(dataChange) {
			try {
//				simulateDao.update(simulate, updates);
				simulateDao.update(simulate);
				
			} catch(Exception e) {
				log.error("[NodeSimulateTrainTimer][syncNodeFromAsset] intelligentDeviceService的listSimulate方法访问异常: {}", e.getMessage());
				throw new BaseException(AnalysisErrorCode.ASSET_SERVICE_ERROR);
			}
		}
	}
	
	private DataVo minimum(PolynomialDataCleaning data, Double optimalValue) {
		DataVo optimalData = new DataVo();
		if(optimalValue == null || optimalValue == 0) {
			optimalData.setDataX(Double.MAX_VALUE);
			optimalData.setDataY(Double.MAX_VALUE);
		}
		for(DataVo vo: data.getData()) {
			if(vo.getDataY() < optimalData.getDataY()) {	
				optimalData.setDataX(vo.getDataX());
				optimalData.setDataY(vo.getDataY());
			}
		}
		return optimalData;
	}
	
	private DataVo minimum(PolynomialDataCleaning data, DataVo optimalData) {
		if(optimalData.getDataY() == null || optimalData.getDataY() == 0) {
			optimalData.setDataY(Double.MAX_VALUE);
		}
		for(DataVo vo: data.getData()) {
			if(vo.getDataY() < optimalData.getDataY()) {	
				optimalData.setDataX(vo.getDataX());
				optimalData.setDataY(vo.getDataY());
			}
			if(vo.getDataY() == optimalData.getDataY() && vo.getDataX() < optimalData.getDataX()) {
				optimalData.setDataX(vo.getDataX());
				optimalData.setDataY(vo.getDataY());
			}
		}
		return optimalData;
	}
	
	private IDataCleaning getDemoData(String type, String datacenterId, String deviceId, Date startTime, Date endTime) {
		Optional<NodeItemVo> opt = assetService.listNode(datacenterId, null).stream().filter( s -> s.getDeviceId().equals(deviceId)).findFirst();	
		IDataCleaning result = new PolynomialDataCleaning();
		if(opt.get() != null) {
			NodeItemVo node = opt.get();
			List<Date> times = new ArrayList<Date>();
			times.add(node.getCreateTime());
			times.add(startTime);
			String start = TimestampUtil.dateToStr(times.stream().max(Comparator.comparingLong(Date::getTime)).get());
			String end = TimestampUtil.dateToStr(endTime);
			List<DemoEntity> demoData = demoDao.list(type, start, end);
			List<DataVo> data = demoData
									.stream()
									.map( s -> {
											String timeStr = TimestampUtil.dateToStr(s.getTimestamp());
											String key = timeStr.substring(0, timeStr.length()-3);
											return new DataVo(key, MeshMathUtil.setScale(2, s.getDataX()), MeshMathUtil.setScale(2, s.getDataY()));
										}
									).collect(Collectors.toList());
			result.setData(data);
		}
		
		return result;
	}
	
	private void initDatacenterMap(String datacenterId) {
		List<Double> values = datacenterMap.get(datacenterId);
		if(values == null) {
			values = new ArrayList<Double>();
			double historyMetric = MeshMathUtil.setScale(2, 250 + rand.nextDouble() * 200);
			values.add(historyMetric);
			double historyValue = MeshMathUtil.setScale(2, 1.3 + rand.nextDouble() * 0.2);
			values.add(historyValue);
			double simulateMetric = MeshMathUtil.setScale(2, 250 + rand.nextDouble() * 200);
			values.add(simulateMetric);
			double simulateValue = MeshMathUtil.setScale(2, 1.4 - rand.nextDouble() * 0.2);
			values.add(simulateValue);
			datacenterMap.put(datacenterId, values);
		}
	}
	
	private void updateDatacenterPue(String datacenterId) {
		List<SimulateEntity> simulates = simulateDao.findAll();
		List<Double> values = datacenterMap.get(datacenterId);
		if(values != null && values.size() == 4) {
			if(simulates != null && values.get(1) != 0 && values.get(3) != 0) {
				List<SimulateEntity> updates = simulates.stream().filter( s -> s.getDatacenterId().equals(datacenterId)).collect(Collectors.toList());
				for(SimulateEntity simulate: updates) {
					simulate.setHistoryValue(values.get(1));
					simulate.setSimulateValue(values.get(3));
					try {
						simulateDao.update(simulate);
					}catch(Exception e) {
						log.info("[NodeSimulateTrainTimer][updateDatacenterPue] {}", e.getMessage());
					}
					
				}
			}
		}
		
	}
	
	private void updateDatacenterHistoryPue(String datacenterId, Double value) {
		List<SimulateEntity> simulates = simulateDao.findAll();
		List<Double> values = datacenterMap.get(datacenterId);
		if(values != null && values.size() == 4) {
			if(simulates != null) {
				List<SimulateEntity> updates = simulates.stream().filter( s -> s.getDatacenterId().equals(datacenterId)).collect(Collectors.toList());
				for(SimulateEntity simulate: updates) {
					simulate.setHistoryValue(value);
					try {
						simulateDao.update(simulate);
					}catch(Exception e) {
						log.info("[NodeSimulateTrainTimer][updateDatacenterPue] {}", e.getMessage());
					}
					
				}
			}
		}
		
	}
	
	private void updateDatacenterSimulatePue(String datacenterId, Double value) {
		List<SimulateEntity> simulates = simulateDao.findAll();
		List<Double> values = datacenterMap.get(datacenterId);
		if(values != null && values.size() == 4) {
			if(simulates != null) {
				List<SimulateEntity> updates = simulates.stream().filter( s -> s.getDatacenterId().equals(datacenterId)).collect(Collectors.toList());
				for(SimulateEntity simulate: updates) {
					simulate.setSimulateValue(value);
					try {
						simulateDao.update(simulate);
					}catch(Exception e) {
						log.info("[NodeSimulateTrainTimer][updateDatacenterPue] {}", e.getMessage());
					}
					
				}
			}
		}
		
	}
	
	private void updateFinalSimualate() {
		List<SimulateEntity> simulates = simulateDao.findAll();
		List<String> datacenterIds = (List<String>)redisUtil.get(DATACENTER_CACHE_KEY);
		if(datacenterIds != null) {
			for(String datacenterId: datacenterIds) {
				try {
					double min = simulates.stream().filter( s -> s.getDatacenterId().equals(datacenterId) && s.getSimulateValue() != null).mapToDouble(s -> s.getSimulateValue()).min().getAsDouble();
					simulateDao.updateSimulateBatch(datacenterId, min);
				}catch(Exception e) {
					log.info(e.getMessage());
				}
			}
		}
	}

} 
